/*******************************************************************************
 * tools.c
 *
 * Copyright (c) 2009 The Lemon Man
 * Copyright (c) 2009 Nicksasa
 * Copyright (c) 2009 WiiPower
 *
 * Distributed under the terms of the GNU General Public License (v2)
 * See http://www.gnu.org/licenses/gpl-2.0.txt for more info.
 *
 * Description:
 * -----------
 *
 ******************************************************************************/

#include <gccore.h>
#include <malloc.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wiiuse/wpad.h>
#include <stdlib.h>

#include "tools.h"

#define ALIGN32(x) (((x) + 31) & ~31)

void *allocate_memory(u32 size){
	void *temp;
	temp = memalign(32, (size+31)&(~31) );
	memset(temp, 0, (size+31)&(~31) );
	return temp;
}

/*
u32 Pad_WaitButtons(void){
	u32 buttons=0;

	for(;;){
		//Obtener botones pulsados de Wiimotes
		buttons=0;
		WPAD_ScanPads();
		//for (cnt = 0; cnt < MAX_WIIMOTES; cnt++)
		//	buttons |= WPAD_ButtonsDown(cnt);
		buttons=WPAD_ButtonsDown(0);
		if(buttons){
			return buttons;
		}
		VIDEO_WaitVSync();

		//Obtener botones pulsados de mandos GC
		buttons=0;
		PAD_ScanPads();
		buttons=PAD_ButtonsDown(0);
		if(buttons){
			if(buttons & PAD_BUTTON_UP)
				return WPAD_BUTTON_UP;
			else if(buttons & PAD_BUTTON_DOWN)
				return WPAD_BUTTON_DOWN;
			else if(buttons & PAD_BUTTON_LEFT)
				return WPAD_BUTTON_LEFT;
			else if(buttons & PAD_BUTTON_RIGHT)
				return WPAD_BUTTON_RIGHT;
			else if(buttons & PAD_BUTTON_A)
				return WPAD_BUTTON_A;
			else if(buttons & PAD_BUTTON_B)
				return WPAD_BUTTON_B;
			else if(buttons & PAD_BUTTON_Y)
				return WPAD_BUTTON_1;
			else if(buttons & PAD_BUTTON_X)
				return WPAD_BUTTON_2;
			else if(buttons & PAD_TRIGGER_R)
				return WPAD_BUTTON_PLUS;
			else if(buttons & PAD_TRIGGER_L)
				return WPAD_BUTTON_MINUS;
			else if(buttons & PAD_BUTTON_START)
				return WPAD_BUTTON_HOME;
		}
		VIDEO_WaitVSync();

	}
}*/

s32 __FileCmp(const void *a, const void *b){
	dirent_t *hdr1 = (dirent_t *)a;
	dirent_t *hdr2 = (dirent_t *)b;
	
	if (hdr1->type == hdr2->type){
		return strcmp(hdr1->name, hdr2->name);
	}else{
		return 0;
	}
}


s32 getdircount(char *path, u32 *cnt)
{
if (cnt == NULL) return -2;
u32 temp = 0;
s32 res = ISFS_ReadDir(path, NULL, &temp);
if (res != ISFS_OK)
{
printf("Error: Could not get dir entry count(ret: %d) for:\n'%s'\n", res, path);
return -1;
}
*cnt = temp;
return 1;
}

s32 getdir(char *path, dirent_t **ent, u32 *cnt){

    if (ent == NULL) return -2;

	s32 res;
	u32 num = 0;

	int i, j, k;
	
	res = getdircount(path, &num);
	if (res < 0){
		//printf("Error: could not get dir entry count! (result: %d)\n", res);
		return -1;
	}

    *cnt = num;
	if (num == 0) return 0;

	char ebuf[ISFS_MAXPATH + 1];

	char *nbuf = (char *)allocate_memory(13 * num);
	if (nbuf == NULL){
		printf("ERROR: could not allocate buffer for name list!\n");
		return -2;
	}

	res = ISFS_ReadDir(path, nbuf, &num);
	DCFlushRange(nbuf,13*num); //quick fix for cache problems?
	if (res != ISFS_OK){
		printf("ERROR: could not get name list! (result: %d)\n", res);
		free(nbuf);
		return -3;
	}
	
	*ent = allocate_memory(sizeof(dirent_t) * num);
	if(*ent==NULL){
		printf("Error: could not allocate buffer\n");
		free(nbuf);
		return -4;
	}

	for (i = 0, k = 0; i < num; i++){	    
		for (j = 0; nbuf[k] != 0; j++, k++)
			ebuf[j] = nbuf[k];
		ebuf[j] = 0;
		k++;

		strcpy((*ent)[i].name, ebuf);
	}
	
	qsort(*ent, *cnt, sizeof(dirent_t), __FileCmp);
	
	free(nbuf);
	return 0;
}
s32 read_full_file_from_nand(char *filepath, u8 **buffer, u32 *filesize)
{
        s32 Fd;
        int ret;

        if (buffer == NULL)
        {
                printf("NULL Pointer\n");
                return -1;
        }

        Fd = ISFS_Open(filepath, ISFS_OPEN_READ);
        if (Fd < 0)
        {
                printf("ISFS_Open %s failed %d\n", filepath, Fd);
                return Fd;
        }

        fstats *status = allocate_memory(sizeof(fstats));
        if (status == NULL)
        {
                printf("Out of memory for status\n");
                ISFS_Close(Fd);
                return -1;
        }
        
        ret = ISFS_GetFileStats(Fd, status);
        if (ret < 0)
        {
                printf("ISFS_GetFileStats failed %d\n", ret);
                ISFS_Close(Fd);
                free(status);
                return -1;
        }
        
        *buffer = allocate_memory(status->file_length);
        if (*buffer == NULL)
        {
                printf("Out of memory for buffer\n");
                ISFS_Close(Fd);
                free(status);
                return -1;
        }
                
        ret = ISFS_Read(Fd, *buffer, status->file_length);
        if (ret < 0)
        {
                printf("ISFS_Read failed %d\n", ret);
                ISFS_Close(Fd);
                free(status);
                free(*buffer);
                return ret;
        }
        ISFS_Close(Fd);

        *filesize = status->file_length;
        free(status);

        return 0;
}

s32 read_file_from_nand(char *filepath, u8 *buffer, u32 filesize)
{
        s32 Fd;
        int ret;

        if (buffer == NULL)
        {
                printf("NULL Pointer\n");
                return -1;
        }

        Fd = ISFS_Open(filepath, ISFS_OPEN_READ);
        if (Fd < 0)
        {
                printf("ISFS_Open %s failed %d\n", filepath, Fd);
                return Fd;
        }

        fstats *status = allocate_memory(sizeof(fstats));
        if (status == NULL)
        {
                printf("Out of memory for status\n");
                ISFS_Close(Fd);
                return -1;
        }
        
        ret = ISFS_GetFileStats(Fd, status);
        if (ret < 0)
        {
                printf("ISFS_GetFileStats failed %d\n", ret);
                ISFS_Close(Fd);
                free(status);
                return -1;
        }
        
        if (filesize > status->file_length)
        {
                printf("Reading %u bytes failed, filesize: %u\n", filesize, status->file_length);
                ISFS_Close(Fd);
                free(status);
                return -1;
        }
        
        ret = ISFS_Read(Fd, buffer, filesize);
        if (ret < 0)
        {
                printf("ISFS_Read failed %d\n", ret);
                ISFS_Close(Fd);
                free(status);
                return ret;
        }
        ISFS_Close(Fd);

        free(status);

        return 0;
}

int load_file_from_nand(const char *filepath, u8 **outbuffer, u32 *outfilesize)
{
        if(!filepath)
                return -1;

        fstats *stats = (fstats *) memalign(32, ALIGN32(sizeof(fstats)));
        if(!stats)
                return IPC_ENOMEM;

        ISFS_Initialize();

        int fd = ISFS_Open(filepath, ISFS_OPEN_READ);
        if(fd < 0)
        {
                free(stats);
                ISFS_Deinitialize();
                return fd;
        }

        int ret = ISFS_GetFileStats(fd, stats);
        if (ret < 0)
        {
                free(stats);
                ISFS_Close(fd);
                ISFS_Deinitialize();
                return ret;
        }

        u32 filesize = stats->file_length;

        free(stats);

        u8 *buffer = (u8 *) memalign(32, ALIGN32(filesize));
        if(!buffer)
        {
                ISFS_Close(fd);
                ISFS_Deinitialize();
                return IPC_ENOMEM;
        }

        ret = ISFS_Read(fd, buffer, filesize);

        ISFS_Close(fd);
        ISFS_Deinitialize();

        if (ret < 0)
        {
                free(buffer);
                return ret;
        }

        *outbuffer = buffer;
        *outfilesize = filesize;

        return 0;
}

s32 Identify_GenerateTik(signed_blob **outbuf, u32 *outlen){
	signed_blob *buffer   = NULL;

	sig_rsa2048 *signature = NULL;
	tik *tik_data  = NULL;

	u32 len;

	/* Set ticket length */
	len = STD_SIGNED_TIK_SIZE;

	/* Allocate memory */
	buffer = (signed_blob *)memalign(32, len);
	if (!buffer)
		return -1;

	/* Clear buffer */
	memset(buffer, 0, len);

	/* Generate signature */
	signature=(sig_rsa2048 *)buffer;
	signature->type = ES_SIG_RSA2048;

	/* Generate ticket */
	tik_data  = (tik *)SIGNATURE_PAYLOAD(buffer);

	strcpy(tik_data->issuer, "Root-CA00000001-XS00000003");
	memset(tik_data->cidx_mask, 0xFF, 32);

	/* Set values */
	*outbuf = buffer;
	*outlen = len;

	return 0;
}


s32 identify(u64 titleid, u32 *ios){
	char filepath[ISFS_MAXPATH] ATTRIBUTE_ALIGN(32);
	u8 *tmdBuffer = NULL;
	u32 tmdSize;
	signed_blob *tikBuffer = NULL;
	u32 tikSize;
	u8 *certBuffer = NULL;
	u32 certSize;
	
	int ret;

	printf("Reading TMD...");
	fflush(stdout);
	
	sprintf(filepath, "/title/%08x/%08x/content/title.tmd", TITLE_UPPER(titleid), TITLE_LOWER(titleid));
	ret = read_full_file_from_nand(filepath, &tmdBuffer, &tmdSize);
	if (ret < 0)
	{
		printf("Reading TMD failed\n");
		return ret;
	}
	printf("done\n");

	*ios = (u32)(tmdBuffer[0x18b]);

	printf("Generating fake ticket...");
	fflush(stdout);
	/*
	sprintf(filepath, "/ticket/%08x/%08x.tik", TITLE_UPPER(titleid), TITLE_LOWER(titleid));
	ret = read_file(filepath, &tikBuffer, &tikSize);
	if (ret < 0)
	{
		printf("Reading ticket failed\n");
		free(tmdBuffer);
		return ret;
	}*/
	Identify_GenerateTik(&tikBuffer,&tikSize);
	printf("done\n");

	printf("Reading certs...");
	fflush(stdout);

	sprintf(filepath, "/sys/cert.sys");
	ret = read_full_file_from_nand(filepath, &certBuffer, &certSize);
	if (ret < 0)
	{
		printf("Reading certs failed\n");
		free(tmdBuffer);
		free(tikBuffer);
		return ret;
	}
	printf("done\n");
	
	printf("ES_Identify...");
	fflush(stdout);

	ret = ES_Identify((signed_blob*)certBuffer, certSize, (signed_blob*)tmdBuffer, tmdSize, tikBuffer, tikSize, NULL);
	if (ret < 0)
	{
		switch(ret)
		{
			case ES_EINVAL:
				printf("Error! ES_Identify (ret = %d;) Data invalid!\n", ret);
				break;
			case ES_EALIGN:
				printf("Error! ES_Identify (ret = %d;) Data not aligned!\n", ret);
				break;
			case ES_ENOTINIT:
				printf("Error! ES_Identify (ret = %d;) ES not initialized!\n", ret);
				break;
			case ES_ENOMEM:
				printf("Error! ES_Identify (ret = %d;) No memory!\n", ret);
				break;
			default:
				printf("Error! ES_Identify (ret = %d)\n", ret);
				break;
		}
		free(tmdBuffer);
		free(tikBuffer);
		free(certBuffer);
		return ret;
	}
	printf("done\n");
	
	free(tmdBuffer);
	free(tikBuffer);
	free(certBuffer);
	return 0;
}

void return_to_channel()
{
    if (TITLE_UPPER(old_title_id) > 1 && TITLE_LOWER(old_title_id) > 2)	// Don't change anything for system menu or no title id
	{
		static u64 sm_title_id  ATTRIBUTE_ALIGN(32);
		sm_title_id = old_title_id; // title id to be launched in place of the system menu

		int ret;
		
		//TODO if the return to channel is not auto, then it needs to be checked if the title exists,
		//but that's a bit complicated when using emulated nand and returning to real nand
		/*
		signed_blob *buf = NULL;
		u32 filesize;

		ret = GetTMD(sm_title_id, &buf, &filesize);
		if (buf != NULL)
		{
			free(buf);
		}

		if (ret < 0)
		{
			return;
		}*/
		
		static ioctlv vector[0x08] ATTRIBUTE_ALIGN(32);

		vector[0].data = &sm_title_id;
		vector[0].len = 8;

		int es_fd = IOS_Open("/dev/es", 0);
		if (es_fd < 0)
		{
			printf("Couldn't open ES module(2)\n");
			return;
		}
		
		ret = IOS_Ioctlv(es_fd, 0xA1, 1, 0, vector);

		IOS_Close(es_fd);
		
		if (ret < 0)
		{
			//printf("ret = %d\n", ret);
		}
	}
}
